const os = require('os')
const http = require('http')
const path = require('path')
const https = require('https')
const fs = require('fs-extra')
const crypto = require('crypto')
const lodash = require('lodash')
const fetch = require('node-fetch')
const FormData = require('form-data')

const funcFns = {
  chromeInstallPath: '',
}

funcFns.ellipsisContent = (content, maxLength) => {
  const ellipsis = '...'
  const ellipsisLength = Buffer.byteLength(ellipsis, 'utf8')

  if (Buffer.byteLength(content, 'utf8') <= maxLength) {
    return content
  }

  const maxContentLength = maxLength - ellipsisLength
  let frontText = ''
  let backText = ''
  let byteCount = 0
  let i = 0,
    j = content.length - 1

  while (i <= j && byteCount < maxContentLength) {
    const frontChar = content[i]
    const backChar = content[j]
    const frontCharBytes = Buffer.byteLength(frontChar, 'utf8')
    const backCharBytes = Buffer.byteLength(backChar, 'utf8')

    if (byteCount + frontCharBytes <= maxContentLength / 2) {
      frontText += frontChar
      byteCount += frontCharBytes
      i++
    }

    if (byteCount + backCharBytes <= maxContentLength) {
      backText = backChar + backText
      byteCount += backCharBytes
      j--
    }

    if (byteCount + frontCharBytes + backCharBytes > maxContentLength) {
      break
    }
  }

  return frontText + ellipsis + backText
}

function truncateString(str, maxLength) {
  let currentLength = 0
  let result = ''

  for (let char of str) {
    const charLength = Buffer.byteLength(char, 'utf8')
    if (currentLength + charLength > maxLength) {
      break
    }
    result += char
    currentLength += charLength
  }

  return result
}

funcFns.truncateText = (text, maxLength) => {
  const truncatedStr = truncateString(text, maxLength)
  return lodash.truncate(truncatedStr, {
    length: maxLength,
    omission: '...',
  })
}

funcFns.sanitizeFilename = (filename) => {
  const sanitizedFilename = filename.replace(/[\/\\:*?"<>|]/g, ' ')
  const maxLength = 255
  const truncatedFilename = sanitizedFilename.substring(0, maxLength)
  return truncatedFilename
}

funcFns.chromePath = () => {
  //return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
  if (funcFns.chromeInstallPath) {
    return funcFns.chromeInstallPath
  }
  const drivers = getDrives()
  for (let driver of drivers) {
    let filePath = getChromePath(driver)
    if (filePath) {
      funcFns.chromeInstallPath = filePath
      return funcFns.chromeInstallPath
    }
  }
  return null
}

function getChromePath(directory) {
  const files = fs.readdirSync(directory)
  for (const file of files) {
    const filePath = `${directory}/${file}`
    if (file.toLowerCase().endsWith('chrome.exe')) {
      return filePath
    }
    let macChromePath = 'Chrome.app/Contents/MacOS/Google Chrome'.toLowerCase()
    if (filePath.toLowerCase().endsWith(macChromePath)) {
      return filePath
    }
    try {
      if (fs.statSync(filePath).isDirectory()) {
        const chromePath = getChromePath(filePath)
        if (chromePath) {
          return chromePath
        }
      }
    } catch (err) {
      //console.log(err)
    }
  }
  return null
}

function getDrives() {
  const drives = []
  const platform = os.platform()

  if (platform === 'win32') {
    const cpus = os.cpus()
    const cpuModel = cpus[0].model

    if (cpuModel.includes('Intel')) {
      for (let i = 67; i <= 90; i++) {
        const drive = String.fromCharCode(i) + ':\\'
        if (fs.existsSync(drive)) {
          drives.push(drive)
        }
      }
    } else if (cpuModel.includes('ARM')) {
      for (let i = 67; i <= 90; i++) {
        const drive = String.fromCharCode(i) + ':\\'
        try {
          fs.accessSync(drive)
          drives.push(drive)
        } catch (err) {
          // Ignore inaccessible drives
        }
      }
    }
  } else if (platform.includes('darwin')) {
    drives.push('/Applications')
  } else {
    drives.push('/')
  }
  return drives
}

funcFns.getFiles = (folderPath) => {
  const filePaths = []
  if (!fs.existsSync(folderPath)) {
    return filePaths
  }
  const items = fs.readdirSync(folderPath)
  for (let item of items) {
    const filePattern = /(jpg|jpeg|png|webp|mp4)/gi.test(item)
    if (!filePattern) {
      continue
    }
    const filePath = path.join(folderPath, item)
    if (fs.statSync(filePath).isFile()) {
      filePaths.push(filePath)
    }
  }
  return filePaths
}

funcFns.getImages = (folderPath) => {
  let files = funcFns.getFiles(folderPath)
  return files.filter((item) => item.match(/\.(png|jpeg|jpg|webp)$/))
}

funcFns.getVideos = (folderPath) => {
  let files = funcFns.getFiles(folderPath)
  return files.filter((item) => item.match(/\.(mp4)$/))
}

funcFns.getImageAndVideos = (folderPath) => {
  let files = funcFns.getFiles(folderPath)
  return files.filter((item) => item.match(/\.(png|jpeg|jpg|webp|mp4)$/))
}

funcFns.downloadFileWithFileName = async (url, folderPath, name) => {
  const ext = funcFns.getFileExtension(url)
  const fileName = name + '.' + ext
  if (!fs.existsSync(folderPath)) {
    fs.mkdirSync(folderPath, { recursive: true })
  }
  const filePath = path.resolve(folderPath, fileName)
  const file = fs.createWriteStream(filePath)
  const protocol = url.startsWith('https') ? https : http
  const request = protocol.get(url, (response) => {
    response.pipe(file)
  })
  return new Promise((resolve, reject) => {
    file.on('finish', () => {
      file.close()
      resolve()
    })

    request.on('error', (error) => {
      fs.unlinkSync(filePath)
      reject(error)
    })
  })
}

funcFns.downloadFile = async (url, folderPath) => {
  const md5 = funcFns.calculateMD5(url)
  return funcFns.downloadFileWithFileName(url, folderPath, md5)
}

funcFns.getFileExtension = (url) => {
  const extname = path.extname(url)
  const queryIndex = extname.indexOf('?')
  if (queryIndex !== -1) {
    return extname.slice(1, queryIndex)
  }
  let ext = extname.slice(1)
  if (!ext) {
    return 'webp'
  }
  return ext
}

funcFns.calculateMD5 = (str) => {
  const hash = crypto.createHash('md5')
  hash.update(str)
  return hash.digest('hex')
}

funcFns.uploadFile = async (filePath, params) => {
  try {
    // 同步读取文件内容
    const data = fs.readFileSync(filePath)
    // 创建FormData对象
    const formData = new FormData()
    formData.append('file', data, { filename: filePath })
    // 发送HTTP请求
    const response = await fetch(params.uploadUrl, {
      method: 'POST',
      body: formData,
      headers: { token: params.token },
    })
    let res = await response.json()
    if (res.code === 0) {
      return res.data.ID
    }
    return null
  } catch (err) {
    console.error('文件上传失败', err)
    return null
  }
}

funcFns.deleteFile = (filePath) => {
  try {
    fs.unlinkSync(filePath)
    return true
  } catch (err) {
    return false
  }
}

funcFns.copyFile = (sourcePath, newPath) => {
  try {
    fs.copyFileSync(sourcePath, newPath)
    return true
  } catch (err) {
    return false
  }
}

funcFns.getContent = (folderPath, fileName) => {
  let contentPath = path.resolve(folderPath, fileName)
  if (!fs.existsSync(contentPath)) {
    return ''
  }
  const content = fs.readFileSync(contentPath, 'utf8')
  return content
}

funcFns.saveContent = (folderPath, fileName, content) => {
  fs.writeFileSync(path.join(folderPath, fileName), content)
}

funcFns.deepTraversal = (obj) => {
  let result = ''
  for (let key in obj) {
    if (key === 'articleUrl') {
      continue
    }
    if (typeof obj[key] === 'object') {
      if (Array.isArray(obj[key])) {
        for (let i = 0; i < obj[key].length; i++) {
          if (i == 0) {
            result += key + ':'
          }
          if (typeof obj[key][i] === 'object') {
            result += funcFns.deepTraversal(obj[key][i])
          } else {
            if (i >= obj[key].length - 1) {
              result += obj[key][i] + ' '
            } else {
              result += obj[key][i] + ','
            }
          }
        }
      } else {
        result += key + ':' + funcFns.deepTraversal(obj[key]) + ' '
      }
    } else {
      result += key + ':' + obj[key] + ' '
    }
  }
  return result
}

export { funcFns }
